﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using CodePlex.Resourceful.Amazon.S3;

namespace CodePlex.Resourceful.Microsoft.Azure
{
    public class BaseAzureClient : Client
    {

        private readonly string _version;
        private readonly string _serviceHost;
        private readonly string _accountName;
        private readonly byte[] _accessKey;
        private readonly bool _usePathStyle;
        private readonly bool _useSSL;

        private TimeSpan? _timeout;

        protected BaseAzureClient(string accountName, string base64key, string serviceHost, bool usePathStyle, bool useSSL, string version)
            : base(ComputeRootUri(accountName, usePathStyle,useSSL, serviceHost))
        {
            _version = version;
            _serviceHost = serviceHost;
            _usePathStyle = usePathStyle;
            _useSSL = useSSL;
            _accountName = accountName;
            _accessKey = Utils.FromBase64(base64key);

        }

       
        public string AccountName
        {
            get
            {
                return _accountName;
            }
        }
        public bool IsSSL
        {
            get
            {
                return _useSSL;
            }
        }
        public string ServiceHost { get { return _serviceHost; } }
        public bool IsUsingPathStyle { get { return _usePathStyle; } }

        public TimeSpan? Timeout
        {
            get
            {
                return _timeout;
            }
            set
            {
                _timeout = value;
            }
        }


        protected static string ComputeRootUri(string accountName, bool usePathStyle, bool useSSL, string serviceHost)
        {

            return string.Format("{0}://{1}/",
                useSSL ? "https" : "http",
                usePathStyle ? (serviceHost + "/" + accountName) : (accountName + "." + serviceHost));

        }







        protected ClientResponse DoAzureRequest(string method, string path, string pathComponent, NameValuePairs pathParameters, NameValuePairs headers)
        {
            return this.DoAzureRequest(method, path, pathComponent, pathParameters, headers, true, null, null);
        }
        protected ClientResponse DoAzureRequest(string method, string path, string pathComponent, NameValuePairs pathParameters, NameValuePairs headers, bool bufferOutput)
        {
            return this.DoAzureRequest(method, path, pathComponent, pathParameters, headers, bufferOutput, null, null);
        }
        protected ClientResponse DoAzureRequest(string method, string path, string pathComponent, NameValuePairs pathParameters, NameValuePairs headers, bool bufferOutput, Stream inputStream, Action<long> bytesTransferred)
        {
            pathParameters = pathParameters ?? new NameValuePairs();
            headers = headers ?? new NameValuePairs();


            if (this.Timeout.HasValue)
            {
                pathParameters.Add("timeout", this.Timeout.Value.TotalSeconds.ToString());
            }
            if (pathComponent != null)
            {
                pathParameters.Add("comp", pathComponent);
            }
            if (inputStream == null && method == HttpMethod.Put)
            {
                inputStream = Utils.ToStream("");
            }


            path = path.Replace(" ", "%20");
            ClientRequest request = ClientRequest.Create(method, Utils.EncodeUrlPath(path, pathParameters), headers, inputStream);
            request.BytesIn = bytesTransferred;
            request.BufferOutput = bufferOutput;

            SignRequest(request, path, pathComponent);

            return this.DoRequest(request);


        }



        virtual protected string SignRequestGetDate(ClientRequest request)
        {
            return "";  // will always be "" since we set the date override header
        }


        virtual protected IEnumerable<string> SignRequestAddCanonicalizedHeaders(ClientRequest request) 
        {
            // Add canonicalized headers

            //To construct the CanonicalizedHeaders portion of the string required for the signature, follow these steps:

            //   1. Retrieve all headers for the resource that begin with x-ms-.
            //   2. Convert each HTTP header name to lowercase.
            //   3. Sort the container of headers lexicographically by header name, in ascending order.
            //   4. TODO Combine headers with the same name into one. The resulting header should be a name-value pair of the format "header-name:comma-separated-value-list", without any white space between values.
            //      Important   The comma-separated list of headers is not ordered by the header values but by the order in which the headers appear in the request. The list of headers must be in the correct order to properly authenticate the request.
            //   5. TODO Replace any breaking white space with by a single space.
            //   6. Trim any white space around the colon in the header.
            //   7. Finally, append a new line character to each canonicalized header in the resulting list. Construct the CanonicalizedNephosHeaders element by concatenating all headers in this list into a single string.

            return Fn.OrderBy(Fn.SelectWhere<KeyValuePair<string, string>, string>(request.Headers,
                 delegate(KeyValuePair<string, string> header)
                 {
                     return header.Key.StartsWith("x-ms-", StringComparison.InvariantCultureIgnoreCase);
                 },
                 delegate(KeyValuePair<string, string> header)
                 {
                     return header.Key.ToLower().Trim() + ":" + header.Value.TrimStart();
                 }));

        }


        private void SignRequest(ClientRequest request, string resource, string component)
        {


            request.Headers[AzureConstants.Headers.MicrosoftDate] = Utils.ToHttpDate(DateTime.UtcNow);
            request.Headers[AzureConstants.Headers.MicrosoftVersion] = _version;
            

            // BLOB AND QUEUE
            //StringToSign = VERB + "\n" +
            //               Content - MD5 + "\n" +
            //               Content - Type + "\n" +
            //               Date + "\n" +
            //               CanonicalizedHeaders +
            //               CanonicalizedResource;

            // TABLE
            //StringToSign = VERB + "\n" +
            //   Content - MD5 + "\n" +
            //   Content - Type + "\n" +
            //   Date + "\n" +   <-- must include!
            //   CanonicalizedResource;


            List<string> tokens = new List<string>();
            tokens.Add(request.Method);
            tokens.Add(request.Headers.GetIfExists(HttpHeader.ContentMD5));
            tokens.Add(request.Headers.GetIfExists(HttpHeader.ContentType));
            tokens.Add(this.SignRequestGetDate(request));

            // add canonicalized headers
            tokens.AddRange(this.SignRequestAddCanonicalizedHeaders(request));
        



            // Add canonicalized resource

            string canonicalizedResource = (_usePathStyle ? ("/" + _accountName) : "") + "/" + _accountName + "/" + resource.Replace(" ", "%20");
            if (component != null)
            {
                canonicalizedResource += "?comp=" + component;
            }
            tokens.Add(canonicalizedResource);



            string stringToSign = Fn.Join("\n", tokens);
            //Console.WriteLine("["+stringToSign+"]");

            // Signature=Base64(HMAC-SHA256(UTF8(StringToSign)))
            string signature = Utils.ToBase64(Utils.ToHMACSHA256(Encoding.UTF8, _accessKey, stringToSign));

            request.Headers[HttpHeader.Authorization] = string.Format("SharedKey {0}:{1}", _accountName, signature);


        }


        protected NameValuePairs MetadataToHeaders(NameValuePairs metadata)
        {
            NameValuePairs headers = new NameValuePairs();
            if (metadata == null)
                return headers;
            foreach (string key in metadata.Keys)
            {
                if (Utils.In(key, HttpHeader.RequestHeaders) || Utils.In(key, HttpHeader.ResponseHeaders) || Utils.In(key, S3Constants.Headers.ContentDisposition))
                {
                    headers.Add(key, metadata[key]);
                }
                else
                {
                    headers.Add(AzureConstants.Headers.MicrosoftMetaPrefix + key, metadata[key]);
                }
            }
            return headers;
        }


  
    }
}
